﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using System.Configuration;
using System.ServiceModel.Description;
using System.ServiceModel.Channels;
using System.Xml;
using PSE.Framework.Configuration;
using System.IO;
using System.Security.Cryptography.X509Certificates;
using PSE.Framework.Common.Security.Services.Configuration;
using PSE.Framework.Common.Security.Services.Authentication.ClientCredentials;

namespace PSE.Framework.Common.Security.Services
{
    public class ServiceInstance<Tproxy> : IDisposable
    {
        private Tproxy _instance;
        private ClientChannel<Tproxy> _serviceChannel;
        private DuplexChannelFactory<Tproxy> _duplexServiceChannel;

        /// <summary>
        /// Retorna uma instância do proxy de acesso a serviços, recuperando automaticamente as configurações de endpoint.
        /// Este método deve ser utilizado pelas componentes do bvmf.
        /// </summary>
        /// <param name="autheticationInformation"></param>
        /// <returns></returns>
        public Tproxy GetInstance(CustomClientCredentials credentials, string contractName)
        {
            return GetInstance(credentials, contractName, null);
        }


        /// <summary>
        /// Retorna uma instância do proxy de acesso a serviços, recuperando automaticamente as configurações de endpoint.
        /// </summary>
        /// <param name="autheticationInformation"></param>
        /// <param name="endpoint"></param>
        /// <param name="configFilePath"></param>
        /// <param name="protocol"></param>
        /// <returns></returns>
        public Tproxy GetInstance(CustomClientCredentials credentials, string contractName, string protocol)
        {
            if (credentials == null)
                throw new ArgumentNullException("credentials");

            if (string.IsNullOrEmpty(contractName))
                throw new ArgumentNullException("contractName");

            string service = typeof(Tproxy).Name;
            //string module = string.Empty, submodule = string.Empty, endpoint = string.Empty;
            string configFilePath = string.Empty;
            string endpointName = string.Empty;
            try
            {
                EndpointConfigurationElement endpointConfig = BindingHelper.RetrieveEndpointConfiguration(contractName);

                //recuperar o caminho do arquivo de configuracao
                configFilePath = endpointConfig.EndpointConfigFile;
                //validar se o arquivo de configuracao de endpoint informado existe
                if (!string.IsNullOrEmpty(configFilePath))
                {
                    configFilePath = Path.Combine(ApplicationConfiguration.GetApplicationRootDirectory(), configFilePath);
                    //concatenar com o caminho base da aplicacao (supondo que tenha informado caminho relativo)
                    if (!File.Exists(configFilePath))
                        throw new Exception(string.Format("The configFile '{0}' don't exists.", configFilePath));
                }
                else
                {
                    throw new Exception(string.Format("The contract '{0}' don't have a configFile configured.", contractName));
                }

                //caso n tenha informado o protocolo, recuperar
                if (string.IsNullOrEmpty(protocol))
                {
                    protocol = endpointConfig.Protocol;
                }

                endpointName = protocol + "." + contractName;
                //Inicializa o canal de comunicação com o servidor.
                _serviceChannel = new ClientChannel<Tproxy>(endpointName, configFilePath, contractName);

                //Remove o ClientCredential default.
                _serviceChannel.Endpoint.Behaviors.Remove(typeof(ClientCredentials));
                //configurar a credencial
                ConfigureCredentials(credentials);
                //Adiciona o BvmfClientCredential.
                _serviceChannel.Endpoint.Behaviors.Add(credentials);

                //Adiciona o BvmfEndpointBehavior
                ClientEndpointBehavior endpointBehavior = new ClientEndpointBehavior();
                _serviceChannel.Endpoint.Behaviors.Add(endpointBehavior);

                //se nao for informado o protocolo recuperar o protocolo configurado para o endpoint
                if (string.IsNullOrEmpty(protocol))
                    protocol = BindingHelper.ResolveProtocol(_serviceChannel.Endpoint);

                if (_serviceChannel.Endpoint.Binding == null)
                    _serviceChannel.Endpoint.Binding = BindingHelper.CreateBinding(protocol);
                else
                    BindingHelper.AddIssuerBinding(_serviceChannel.Endpoint.Binding);

                //Alteração da propriedade MaxArrayLength de 16384 para 512000 caso o Binding seja um CustomBinding
                if (_serviceChannel.Endpoint.Binding is CustomBinding)
                {
                    BindingContext bindingContext = new BindingContext((CustomBinding)_serviceChannel.Endpoint.Binding, new BindingParameterCollection());
                    bindingContext.GetInnerProperty<XmlDictionaryReaderQuotas>().MaxArrayLength = Int32.MaxValue;
                }

                foreach (OperationDescription op in _serviceChannel.Endpoint.Contract.Operations)
                {
                    var dcSerializer = op.Behaviors.Find<DataContractSerializerOperationBehavior>();
                    if (dcSerializer != null)
                        dcSerializer.MaxItemsInObjectGraph = Int32.MaxValue;
                }

                //Retorna uma instância da interface, utilizando o canal configurado.
                _instance = _serviceChannel.CreateChannel();

                return _instance;

            }
            catch (Exception ex)
            {
                //pega o namespace do contrato do serviço
                string error = "An error occoured trying to generate the service channel. \n" +
                               "Please verify if the endpoint configured in the file \"{0}\" has the name \"{1}\". \n" +
                               "If the endpoint name is correct, check if the endpoint contract attribute is {2}\n";

                throw new CommunicationException(String.Format(error, configFilePath, endpointName, contractName), ex);
            }
        }

        public Tproxy GetDuplexChannelInstance(CustomClientCredentials credentials, object instanceCallBack, EndpointAddress endpoint)
        {
            if (credentials == null)
                throw new ArgumentNullException("credentials");

            try
            {
                if (_duplexServiceChannel == null)
                {
                    _duplexServiceChannel = new DuplexChannelFactory<Tproxy>(new InstanceContext(instanceCallBack), BindingHelper.CreateBinding(BindingHelper.ResolveProtocol(endpoint)), endpoint);

                    //Remove o ClientCredential default.
                    _duplexServiceChannel.Endpoint.Behaviors.Remove(typeof(ClientCredentials));

                    //configurar a credencial
                    ConfigureCredentials(credentials);
                    //Adiciona o BvmfClientCredential.
                    _duplexServiceChannel.Endpoint.Behaviors.Add(credentials);

                    //Adiciona o BvmfEndpointBehavior
                    ClientEndpointBehavior endpointBehavior = new ClientEndpointBehavior();
                    _duplexServiceChannel.Endpoint.Behaviors.Add(endpointBehavior);

                    _instance = _duplexServiceChannel.CreateChannel(new InstanceContext(instanceCallBack));
                }

                return _instance;
            }
            catch (Exception ex)
            {
                throw new CommunicationException(String.Format("Could not connect create channel to address {0}. See inner exceptions for more details.", endpoint.Uri.ToString()), ex);
            }
        }

        #region ConfigureCredentials
        /// <summary>
        /// Realizar as configurações necessárias nas Credentials informadas
        /// </summary>
        /// <param name="credentials"></param>
        protected virtual void ConfigureCredentials(CustomClientCredentials credentials)
        {
            CertificateConfigurationElement certificate = ApplicationConfiguration.GetCurrent().GetSection<CertificateConfigurationSection>("certificateConfiguration").STSAuthorityCertificate;
            //configura o certificado do serviço.
            credentials.ServiceCertificate.SetDefaultCertificate(certificate.FindValue, certificate.StoreLocation, certificate.StoreName);
            credentials.ServiceCertificate.Authentication.CertificateValidationMode = System.ServiceModel.Security.X509CertificateValidationMode.None;
        }
        #endregion

        #region IDisposable Members

        public void Dispose()
        {
            if (_serviceChannel != null)
            {
                try
                {
                    if (_serviceChannel.State != CommunicationState.Faulted)
                        _serviceChannel.Close();
                }
                catch
                {
                    _serviceChannel.Abort();
                }
            }

            if (_duplexServiceChannel != null)
            {
                try
                {
                    if (_duplexServiceChannel.State != CommunicationState.Faulted)
                        _duplexServiceChannel.Close();
                }
                catch
                {
                    _duplexServiceChannel.Abort();
                }
            }
        }
        #endregion
    }
}
